package btctx

import (
	"encoding/hex"
	"errors"
	"fmt"

	"gitee.com/coin-kit/btc-tx/address"
	"gitee.com/coin-kit/btc-tx/txscript"
	"github.com/btcsuite/btcd/wire"
)

type Builder struct {
	err     error
	fee     uint64
	params  *address.Params
	refund  string
	coins   []*Coin
	outputs []*wire.TxOut
}

func NewBuilder() *Builder {
	builder := new(Builder)
	builder.coins = []*Coin{}
	builder.outputs = []*wire.TxOut{}
	return builder
}

func (b *Builder) SetFee(fee uint64) {
	if b.err != nil {
		return
	}
	b.fee = fee
	return
}

func (b *Builder) SetParams(params *address.Params) {
	if b.err != nil {
		return
	}
	b.params = params
	return
}

func (b *Builder) SetRefund(wifaddr string) {
	if b.err != nil {
		return
	}
	b.refund = wifaddr
	return
}

func (b *Builder) SetCoins(coins []*Coin) {
	if b.err != nil {
		return
	}
	for _, coin := range coins {
		pk_script, err := hex.DecodeString(coin.PkScript)
		if err != nil {
			b.err = err
			return
		}
		script := txscript.NewScriptFromBytes(pk_script)
		if script.IsP2PKH() == false {
			b.err = errors.New("From coin only support p2pkh address")
			return
		}
	}
	b.coins = coins
	return
}

func (b *Builder) AddTxOut(wifaddr string, amount uint64) {
	if b.err != nil {
		return
	}
	if b.params == nil {
		b.err = errors.New("No set of address params")
		return
	}
	addr, err := address.FromWIFAddress(wifaddr, b.params)
	if err != nil {
		b.err = err
		return
	}
	pk_script := addr.ToPkScript()
	b.outputs = append(b.outputs, wire.NewTxOut(int64(amount), pk_script))
	return
}

func (b *Builder) Build() ([]byte, error) {
	if b.err != nil {
		return nil, b.err
	}
	// 计算总输出
	out_total := b.fee
	for _, item := range b.outputs {
		out_total = out_total + uint64(item.Value)
	}
	// 挑选最佳输入
	in_total := uint64(0)
	used := []*Coin{}
	for i := 0; i < len(b.coins) && in_total < out_total; i++ {
		coin := b.coins[i]
		used = append(used, coin)
		in_total = in_total + coin.Amount
	}
	b.coins = used
	// 配平
	if in_total < out_total {
		return nil, fmt.Errorf("shortage of coin %d < %d %d", in_total, out_total, len(b.coins))
	}
	// 找零
	balance := in_total - out_total
	if b.refund == "" {
		return nil, errors.New("refund address is empty")
	}
	if balance > 0 {
		b.AddTxOut(b.refund, balance)
		if b.err != nil {
			return nil, b.err
		}
	}
	// 构建
	tx := wire.NewMsgTx(1)
	tx.LockTime = 0
	tx.TxOut = b.outputs
	tx.TxIn = []*wire.TxIn{}
	for _, coin := range b.coins {
		txin, err := coin.ToTxIn()
		if err != nil {
			return nil, err
		}
		tx.TxIn = append(tx.TxIn, txin)
	}
	return EncodeTxBytes(tx)
}
